package com.lorizz.docrepo.security;

import java.io.IOException;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.Credentials;
import javax.jcr.SimpleCredentials;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;

import org.apache.jackrabbit.core.security.authentication.CredentialsCallback;
import org.apache.jackrabbit.core.security.principal.AdminPrincipal;

import com.lorizz.docrepo.DocrepoException;

public class LoginModule implements javax.security.auth.spi.LoginModule {

    private Subject subject;
    private CallbackHandler callbackHandler;
    @SuppressWarnings("unused")
    private Map<String, ?> sharedState;
    private String authenticationServiceClassName;
    private AuthenticationService authenticationService;
    private SimpleCredentials credentials;
    private String adminId;

    @Override
    public boolean abort() throws LoginException {
        callbackHandler = null;
        credentials = null;
        return logout();
    }

    @Override
    public boolean commit() throws LoginException {
        if (isAdmin(credentials.getUserID())) {
            subject.getPrincipals().add(new AdminPrincipal(credentials.getUserID()));
            return true;
        }
        List<String> roles = authenticationService.getRoles(credentials.getUserID());
        subject.getPrincipals().add(new RepositoryPrincipal(credentials.getUserID()));
        for (String role : roles) {
            subject.getPrincipals().add(new RepositoryPrincipal(role));
        }
        subject.getPublicCredentials().add(credentials);
        return true;
    }

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler,
            Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;

        adminId = options.get("adminId").toString();
        authenticationServiceClassName = options.get("authenticationService")
                .toString();
        try {
            authenticationService = (AuthenticationService) Class.forName(
                    authenticationServiceClassName).newInstance();
            authenticationService.init(options);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean login() throws LoginException {
        Credentials cred = getCredentials();
        if (!(cred instanceof SimpleCredentials))
            throw new DocrepoException("Only support SimpleCredentials");
        credentials = (SimpleCredentials) cred;
        String username = credentials.getUserID();
        String password = new String(credentials.getPassword());
        if (isAdmin(username))
            return true;
        if (authenticationService.authenticate(username, password)) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(String username) {
        return adminId.equals(username);
    }

    @Override
    public boolean logout() throws LoginException {
        Set<Principal> thisPrincipals = subject.getPrincipals();
        Set<SimpleCredentials> thisCredentials = subject.getPublicCredentials(SimpleCredentials.class);
        if (thisPrincipals == null || thisCredentials == null
                || thisPrincipals.isEmpty() || thisCredentials.isEmpty()) {
            return false;
        } else {
            thisPrincipals.clear();
            thisCredentials.clear();
            return true;
        }
    }

    private Credentials getCredentials() {
        CredentialsCallback callback = new CredentialsCallback();
        try {
            callbackHandler.handle(new Callback[] { callback });
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedCallbackException e) {
            throw new RuntimeException(e);
        }
        Credentials credentials = callback.getCredentials();
        return credentials;
    }

}
